package jsonrpc

import (
	"encoding/json"
	"fmt"
	"github.com/kataras/iris/v12/context"
	"github.com/kataras/iris/v12/core/router"
	"io"
	"lib/exception"
	"strings"
)

func ApiHandler(party router.Party) {
	party.Post("", func(context *context.Context) {
		contentType := getHeader(context, "content-type")
		if !strings.HasPrefix(contentType, "json") {
			_ = context.JSON(BuildErrorResponse(SingleRequest{Id: 0}, BadRequestContent()))
			return
		}
		requests, parseErr := parseRequests(context)
		if parseErr != nil {
			_ = context.JSON(BuildErrorResponse(SingleRequest{Id: 0}, BadRequestContent()))
			return
		}
		// handle single request
		if len(requests) == 1 {
			// invoke
			request := requests[0]
			_, result, responseError := invokeSingleRequest(request)
			if responseError.Code > 0 {
				_ = context.JSON(BuildErrorResponse(request, *responseError))
			} else {
				_ = context.JSON(BuildResponse(request, result))
			}
			return
		}
		// handle multiple request
		_ = context.JSON(invokeBatchRequests(requests))
	})
}

func invokeBatchRequests(requests []SingleRequest) []SingleResponse {
	var channels []chan SingleResponse
	var result []SingleResponse
	for _, request := range requests {
		ch := make(chan SingleResponse)
		channels = append(channels, ch)
		requestToUse := request
		go func() {
			invokeSingleRequestAsync(ch, requestToUse)
		}()
	}
	for _, channel := range channels {
		singleResult := <-channel
		result = append(result, singleResult)
		close(channel)
	}
	return result
}

func invokeSingleRequestAsync(ch chan SingleResponse, request SingleRequest) {
	var sentResponse = false
	defer func() {
		if sentResponse {
			return
		}
		// send default response
		ch <- BuildErrorResponse(request, SingleResponseError{
			Code:    500,
			Message: "Unexpected request handling error.",
		})
	}()
	_, result, responseError := invokeSingleRequest(request)
	var response SingleResponse
	if responseError.Code > 0 {
		response = BuildErrorResponse(request, *responseError)
	} else {
		response = BuildResponse(request, result)
	}
	ch <- response
	sentResponse = true
}

func invokeSingleRequest(request SingleRequest) (uint, interface{}, *SingleResponseError) {
	methodName := request.Method
	handler, getHandlerErr := GetRpcHandler(methodName)
	if getHandlerErr != nil {
		responseErr := MethodNotFound(methodName)
		return request.Id, nil, &responseErr
	}
	params, paramDecodeErr := handler.Decoder(request.Params)
	if paramDecodeErr != nil {
		responseErr := MethodParamDecodeError(methodName, paramDecodeErr)
		return request.Id, nil, &responseErr
	}
	invokerResult, invokerErr := handler.Command(params)
	responseError := SingleResponseError{}
	if invokerErr != nil {
		errCode, err := exception.ErrCode(invokerErr)
		responseError = SingleResponseError{
			Code:    uint(errCode),
			Message: err.Error(),
		}
	}
	return request.Id, invokerResult, &responseError
}

func getHeader(ctx *context.Context, headerName string) string {
	headers := ctx.Request().Header
	headerLower := strings.ToLower(headerName)
	for k, v := range headers {
		if strings.ToLower(k) == headerLower {
			return v[0]
		}
	}
	return ""
}

func parseRequests(context *context.Context) ([]SingleRequest, exception.Exception) {
	allBytes, readErr := io.ReadAll(context.Request().Body)
	if readErr != nil {
		return nil, nil
	}
	jsonString := string(allBytes)
	jsonString = strings.TrimSpace(jsonString)
	var result []SingleRequest
	if strings.HasPrefix(jsonString, "[") {
		jsonString = fmt.Sprintf("[%v]", jsonString)
	}
	unmarshalErr := json.Unmarshal([]byte(jsonString), &result)
	if unmarshalErr != nil {
		return result, unmarshalErr
	}
	return result, nil
}
